package com.bitters.utils;

import com.alibaba.fastjson.JSON;
import com.bitters.domain.RedPacketBasic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Random;

/**
 * @author: thomashe
 * @Date: 2023/11/15/ 15:35
 * @Description: 拆红包工具类 二倍均值法(微信红包采用此法)
 * 还是以10元10个红包为例，去除每个红包的最小金额后，红包剩余9.9元，二倍均值计算公式：2 * 剩余金额/剩余红包数
 * 第一个红包在[0,1.98]范围随机，假设随机得1.9，则第一个红包金额为2.0，红包剩余8元。
 * 第二个红包在[0,2]范围随机，假设随机的1元，则第二个红包金额为1.1元，红包剩余7元。
 * 第三个红包在[0,2]范围随机，假设随机的0.5元，则第三个红包金额为0.6元，红包剩余5.5元。
 * 以此类推。
 * <p>
 * 此工具类使用版本——原理
 * 剩余红包金额M，剩余人数N，那么：每次抢到金额=随机(0，M/N*2)
 * 保证了每次随机金额的平均值是公平的
 * 假设10人，红包金额100元
 * 第一人：100/10*2=20，随机范围(0,20)，平均可以抢到10元
 * 第二人：90/9*2=20，随机范围(0,20)，平均可以抢到10元
 * 第三人：80/8*2=20，随机范围(0,20)，平均可以抢到10元
 * 以此类推，每次随机范围的均值是相等的
 * <p>
 * 缺点：除了最后一次，任何一次抢到的金额都不会超过人均金额的两倍，并不是任意的随机
 */

@Component
@Slf4j
public class RedPacketUtils {

    /**
     * 红包的缓存key
     */
    public static final String RED_PACKAGE_KEY = "RED_PACKAGE_KEY_";

    /**
     * 抢红包资格缓存key
     */
    public static final String GRAB_RED_PACKAGE_KEY = "GRAB_RED_PACKAGE_KEY_";

    /**
     * 拆红包的分布式锁key
     */
    public static final String UNPACK_RED_PACKAGE_KEY = "UNPACK_RED_PACKAGE_KEY_";

    /**
     * 抢中红包的缓存key
     */
    public static final String RED_PACKAGE_CONSUME_KEY = "RED_PACKAGE_CONSUME_KEY_";

    /**
     * 红包缓存时效时间(没人抢、最大为一天, 红包抢完缓存主动删除)，毫秒
     */
    public static final int RED_PACKET_EXPIRE = 24 * 60 * 60;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 拆红包核心接口
     *
     * @param redPacketId 红包id
     * @param userId      用户id
     * @return
     */
    public Integer unpackRedPacket(String redPacketId, String userId) {
        Integer redPacketAmount = null;
        try {
            boolean tryLock = redisUtil.tryLock(UNPACK_RED_PACKAGE_KEY + redPacketId);
            if (!tryLock) {
                //分布式锁获取失败, 将抢红包资格加回去, 让其他人可以去抢
                redisUtil.incr(GRAB_RED_PACKAGE_KEY + redPacketId, 1);
                return redPacketAmount;
            }
            //红包信息缓存
            String redPackageKey = RED_PACKAGE_KEY + redPacketId;

            //获取红包相关信息
            RedPacketBasic redPacket = (RedPacketBasic) redisUtil.get(redPackageKey);
            if (redPacket == null) {
                return redPacketAmount;
            }
            //红包剩下一个
            if (redPacket.getTotalPacket() == 1) {
                redPacket.setTotalPacket(redPacket.getTotalPacket() - 1);
                redPacketAmount = redPacket.getTotalAmount();
                redPacket.setTotalAmount(0);
            }

            if (redPacket.getTotalPacket() > 1) {
                //红包金额以分为单位, 使用random.nextInt(Integer)，最后在再除以100
                //这样就可以保证每个人抢到的金额都可以精确到小数点后两位
                Integer restAmount = redPacket.getTotalAmount();

                Random random = new Random();

                // 随机范围：[1，剩余人均金额的两倍)，左闭右开
                redPacketAmount = random.nextInt(restAmount / redPacket.getTotalPacket() * 2 - 1) + 1;

                //红包数除以100
                redPacket.setTotalAmount(redPacket.getTotalAmount() - redPacketAmount);
                redPacket.setTotalPacket(redPacket.getTotalPacket() - 1);
            }

            //成功抢到红包
            if (redPacketAmount != null && redPacketAmount > 0) {
                //加入已抢到红包的hash缓存中
                redisUtil.hSet(RED_PACKAGE_CONSUME_KEY + redPacketId, userId, redPacketAmount);

                // 将剩余红包个数和剩余总金额存入缓存
                redisUtil.set(RedPacketUtils.RED_PACKAGE_KEY + redPacketId, redPacket, RedPacketUtils.RED_PACKET_EXPIRE);
            }

            //红包被抢完，清除缓存
            if (redPacket.getTotalPacket() <= 0) {
                log.info("红包已被抢完, redPacketId:{}, redPacket:{}", redPacketId, JSON.toJSONString(redPacket));
                //抢红包资格缓存key
                String grabRedPackageKey = GRAB_RED_PACKAGE_KEY + redPacketId;
                //抢中红包的缓存key
                String redPackageConsumeKey = RED_PACKAGE_CONSUME_KEY + redPacketId;

                //清楚该红包相关的缓存
                redisUtil.del(redPackageKey, grabRedPackageKey, redPackageConsumeKey);
            }
            log.info("抢红包SUCCESS, redPacketId:{}, redPacketAmount:{}", redPacketId, redPacketAmount);
            return redPacketAmount;
        } catch (Exception e) {
            log.error("抢红包发生异常", e);
            throw e;
        } finally {
            redisUtil.unLock(UNPACK_RED_PACKAGE_KEY + redPacketId);
        }

    }

    // Lua脚本
    String script = "local listKey = 'red_packet_list_'..KEYS[1] \n" + "local redPacket = 'red_packet_'..KEYS[1] \n"
            + "local stock = tonumber(redis.call('hget', redPacket, 'stock'))\n" + "if stock <= 0 then return 0 end \n"
            + "stock = stock -1 \n" + "redis.call ('hset', redPacket, 'stock', tostring (stock)) \n"
            + "redis.call('rpush', listKey, ARGV[1]) \n" + "if stock == 0 then return 2 end \n" + "return 1 \n";
    // 在缓存Lua脚本后，使用该变量保存Redis返回的32位的SHA1编码，使用它去执行缓存的 Lua脚本
    String sha1 = null;
    //先判断红包是否为空，再判断是否已经抢过此红包，最后再记录抢红包
    String lua = "if ( redis.call('hexists',KEYS[2],KEYS[3]) == 1 ) then " + //说明已经抢过了
            " return '已经抢过了' " +
            " elseif ( redis.call('llen',KEYS[1]) == 0 ) then " +//说明已经抢完了
            " return '已经抢完了' " +
            " else " +//说明开始抢红包
            " local number = redis.call('lpop',KEYS[1]) " +
            " redis.call('hset',KEYS[2],KEYS[3],number) " +
            " return tostring(number) " + " end ";
    //stringRedisTemplate.execute(new DefaultRedisScript<>(lua, String.class), list, "1");
}